﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web;

/// <summary>
/// This class containing all methods, properties and enums used for user maintenance
/// </summary>
public class UserManagement
{
    /// <summary>
    /// current user
    /// </summary>
    User currentUser;

    /// <summary>
    /// pass a user to this instance
    /// </summary>
    /// <param name="currentUsername">current logged in username</param>
    public UserManagement(string currentUsername)
    {
        this.currentUser = GetUser(currentUsername);
    }

    /// <summary>
    /// the current logged in user will be used for this instance
    /// </summary>
    public UserManagement()
    {
        this.currentUser = GetCurrentUser();
    }

    /// <summary>
    /// Current user that this instance is working on
    /// </summary>
    public User CurrentUser
    {
        get
        {
            return this.currentUser;
        }
    }

    /// <summary>
    /// user roles and their rights in application
    /// compliant with column Role in table User
    /// </summary>
    public enum Role
    {
        Guest, // right: view assigned projects
        Developer, // right: view assigned projects, comment on bugs
        Tester, // right: view assigned projects, add/edit/comment/change status of bugs, assign/remove assignment of bugs to project Developers and Designers
        [Description("Project Lead")]
        ProjectLead, //right: as Tester's, change project info, change project status, add/remove users to/from project (project users)
        [Description("Administrator")]
        Admin // all rights
    }

    /// <summary>
    /// short names version of the above
    /// </summary>
    public enum ShortRole
    {
        Guest, // right: view assigned projects
        Developer, // right: view assigned projects, comment on bugs
        Tester, // right: view assigned projects, add/edit/comment/change status of bugs, assign/remove assignment of bugs to project Developers and Designers
        ProjectLead, //right: as Tester's, change project info, change project status, add/remove users to/from project (project users)
        Admin // all rights
    }

    /// <summary>
    /// create a new user to system
    /// </summary>
    /// <param name="username">username, alphanumeric only, must be unique</param>
    /// <param name="password">password will be hashed</param>
    /// <param name="fullname">full real name</param>
    /// <param name="email">email address, must be valid to receive notifications</param>
    /// <param name="role">role Id, getting role from enum (int)Role.[Role Name]</param>
    /// <param name="organization">user's organization from enum Organization</param>
    /// <returns>true if new user created succeeds, otherwise false with exeption thrown</returns>
    public bool CreateUser(string username, string password, string fullname, string email, int role,
                           string organization)
    {
        try
        {
            // create a new user
            User newUser = new User();

            // username, NOTE: unique
            newUser.Username = username;

            // hash the password using MD5 algorithm with custom salt, return a 32-char string
            newUser.Password = PasswordHash.GetMd5Hash(password);

            // set required settings
            newUser.Fullname = fullname;
            newUser.Email = email;
            newUser.Role = role;

            // set optional settings to default values
            newUser.Organization = organization;
            // default email notification set to true
            newUser.EnableEmailNotification = true;
            // default bugs displayed per page set to 10
            newUser.BugsPerPage = ApplicationGlobalVariables.DefaultBugsPerPage;
            // active current user
            newUser.IsActive = true;

            // add new user to database
            ApplicationGlobalVariables.KillDaBugsDatabase.Users.InsertOnSubmit(newUser);
            ApplicationGlobalVariables.KillDaBugsDatabase.SubmitChanges();

            // add this event to log
            Logging.AddLog(username);

            // if everything alright
            return true;
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// update an existing user, based on username
    /// </summary>
    /// <param name="username">username to be updated, note: username cannot be changed</param>
    /// <param name="password">new password, leave empty or null will not update</param>
    /// <param name="fullname">new full name</param>
    /// <param name="email">new email</param>
    /// <param name="role">new user role ID</param>
    /// <param name="enableEmailNotification">new email notification policy</param>
    /// <param name="organization">new organization</param>
    /// <param name="customInfo">new custom info</param>
    /// <param name="bugsPerPage">new number of bugs displayed per page</param>
    /// <returns>true if new user updated succeeds, otherwise false with exeption thrown</returns>
    public bool UpdateUser(string username, string password, string fullname, string email,
                           int role, bool enableEmailNotification, string organization,
                           string customInfo, int bugsPerPage)
    {
        try
        {
            User newUser = GetUser(username);

            // only change password in case user enters a new password
            if (!string.IsNullOrWhiteSpace(password))
                newUser.Password = PasswordHash.GetMd5Hash(password);

            // update new name and email
            newUser.Fullname = fullname;
            newUser.Email = email;

            // only update role if needed
            if (role != newUser.Role) newUser.Role = role;

            // set custom fields
            newUser.EnableEmailNotification = enableEmailNotification;
            newUser.Organization = organization;
            newUser.BugsPerPage = bugsPerPage;
            newUser.CustomInfo = customInfo;

            // save changes to database
            ApplicationGlobalVariables.KillDaBugsDatabase.SubmitChanges();

            // add this event to log
            Logging.AddLog(Logging.LogEvent.UpdateUser, newUser);

            // if everything alright
            return true;
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// update current user
    /// </summary>
    /// <param name="username">username to be updated, note: username cannot be changed</param>
    /// <param name="password">new password, leave empty or null will not update</param>
    /// <param name="fullname">new full name</param>
    /// <param name="email">new email</param>
    /// <param name="enableEmailNotification">new email notification policy</param>
    /// <param name="organization">new organization</param>
    /// <param name="customInfo">new custom info</param>
    /// <param name="bugsPerPage">new number of bugs displayed per page</param>
    /// <returns>true if new user updated succeeds, otherwise false with exeption thrown</returns>
    public bool UpdateUser(string username, string password, string fullname, string email,
                           bool enableEmailNotification, string organization, string customInfo,
                           int bugsPerPage)
    {
        try
        {
            User newUser = currentUser;

            // only change password in case user enters a new password
            if (!string.IsNullOrWhiteSpace(password))
                newUser.Password = PasswordHash.GetMd5Hash(password);

            // update new name and email
            newUser.Fullname = fullname;
            newUser.Email = email;

            // set custom fields
            newUser.EnableEmailNotification = enableEmailNotification;
            newUser.Organization = organization;
            newUser.BugsPerPage = bugsPerPage;
            newUser.CustomInfo = customInfo;

            // save changes to database
            ApplicationGlobalVariables.KillDaBugsDatabase.SubmitChanges();

            // add this event to log
            Logging.AddLog(Logging.LogEvent.UpdateUser, newUser);

            // if everything alright
            return true;
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// Active or UnActive the User
    /// </summary>
    /// <param name="username">username of actived</param>
    /// <param name="active">node of user (Active or Not Active) </param>
    /// <returns>result True or False </returns>
    public bool UpdateUser(string username, bool active)
    {
        try
        {
            User newUser = GetUser(username);

            // only update role if needed
            if (active != newUser.IsActive) newUser.IsActive = active;

            // save changes to database
            ApplicationGlobalVariables.KillDaBugsDatabase.SubmitChanges();

            // add this event to log
            Logging.AddLog(Logging.LogEvent.UpdateUser, newUser);

            // if everything alright
            return true;
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// enable or disable a user
    /// </summary>
    /// <param name="username">username of user</param>
    /// <param name="isActive">true to enable, false to diable</param>
    /// <returns>true if enable or disable successfully, otherwise false</returns>
    public bool EnableDisableUser(string username, bool isActive)
    {
        try
        {
            User disabledUser = GetUser(username);
            disabledUser.IsActive = isActive;
            ApplicationGlobalVariables.KillDaBugsDatabase.SubmitChanges();

            // add this event to log
            Logging.AddLog(Logging.LogEvent.EnableUser, disabledUser);

            // return
            return true;
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// enable or disable current user
    /// </summary>
    /// <param name="isActive">true to enable, false to diable</param>
    /// <returns>true if enable or disable successfully, otherwise false</returns>
    public bool EnableDisableUser(bool isActive)
    {
        try
        {
            currentUser.IsActive = isActive;
            ApplicationGlobalVariables.KillDaBugsDatabase.SubmitChanges();

            // add this event to log
            Logging.AddLog(Logging.LogEvent.EnableUser, currentUser);

            // return
            return true;
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// delete an existing user, based on username
    /// </summary>
    /// <param name="username">username of the user to be deleted</param>
    /// <returns>true if new user deletion succeeds, otherwise false with exeption thrown</returns>
    public bool DeleteUser(string username)
    {
        try
        {
            // delete database records
            User deletedUser = GetUser(username);
            ApplicationGlobalVariables.KillDaBugsDatabase.Users.DeleteOnSubmit(deletedUser);
            ApplicationGlobalVariables.KillDaBugsDatabase.SubmitChanges();

            // delete logs
            Logging.ClearLogUser(username);

            // if everything alright
            return true;
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// delete current user
    /// </summary>
    /// <returns>true if new user deletion succeeds, otherwise false with exeption thrown</returns>
    public bool DeleteUser()
    {
        try
        {
            // delete database records
            ApplicationGlobalVariables.KillDaBugsDatabase.Users.DeleteOnSubmit(currentUser);
            ApplicationGlobalVariables.KillDaBugsDatabase.SubmitChanges();

            // delete logs
            Logging.ClearLogUser(CurrentUser.Username);

            // if everything alright
            return true;
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// gets username of current logged in user
    /// </summary>
    /// <returns>username of current logged in user, NULL if no user is logged in</returns>
    public static string GetCurrentUsername()
    {
        try
        {
            // get current cookie of user
            HttpCookie cookie = HttpContext.Current.Request.Cookies["userInfo"];
            // get username from cookie
            string username = cookie.Values["username"];
            // if no username found, no user is logging in
            if (string.IsNullOrEmpty(username)) return null;
            // if check hash on cookie matches that of application variable, current username is logging in
            if (cookie.Values["token"] == HttpContext.Current.Application[username] as string)
                return username;
            // elsewhile, user session has expired
            else
                return null;
        }
        catch (Exception ex)
        {
            return null;
            throw ex;
        }
    }

    /// <summary>
    /// Get current logged in user, can be used to check if a user is currently logging in
    /// </summary>
    /// <returns>username, null if user is not logging in</returns>
    public static User GetCurrentUser()
    {
        try
        {
            string username = GetCurrentUsername();
            if (username == null) return null;
            return GetUser(username);
        }
        catch (Exception ex)
        {
            return null;
            throw ex;
        }
    }

    /// <summary>
    /// get User object of a username
    /// </summary>
    /// <param name="username">username</param>
    /// <returns>User object</returns>
    public static User GetUser(string username)
    {
        try
        {
            return ApplicationGlobalVariables.KillDaBugsDatabase.Users.Single(i => i.Username == username);
        }
        catch (Exception ex)
        {
            return null;
            throw ex;
        }
    }

    /// <summary>
    /// Save current user account info to cookie and application variable
    /// </summary>
    /// <param name="username">username to save</param>
    /// <param name="rememberMe">true to remember login info</param>
    public static void SaveLoginInfoToCookie(string username, bool rememberMe)
    {
        // create a new cookie
        HttpCookie cookie = new HttpCookie("userInfo");
        // set cookie to HttpOnly so that client script (ie javascript) cannnot access this cookie, thus prevent cross site scripting attacks
        cookie.HttpOnly = true;
        // if remember login info, set expiration date to some day in future
        if (rememberMe)
        {
            cookie.Expires = DateTime.Now.AddDays(ApplicationGlobalVariables.DaysUntilReLogin);
            cookie.Values["remember"] = "1";
        }
        // save username to cookie
        cookie.Values["username"] = username;
        // add a token (random hash) for security check
        // save to application variable and user cookie
        // used random number to ensure token is unique
        Random randomNumber = new Random();
        HttpContext.Current.Application[username] = cookie.Values["token"] = PasswordHash.GetMd5Hash(username + DateTime.Now.ToLongTimeString() + randomNumber.Next().ToString());
        // add this cookie to Cookies Collection
        HttpContext.Current.Response.Cookies.Add(cookie);
    }

    /// <summary>
    /// Check if a pair of username and password is correct
    /// </summary>
    /// <param name="username">username to check</param>
    /// <param name="password">password to check</param>
    /// <returns>true if login info is correct, otherwise false</returns>
    public static bool UserLoginInfoIsCorrect(string username, string password)
    {
        try
        {
            var query = from item in ApplicationGlobalVariables.KillDaBugsDatabase.Users
                        where item.Username == username && PasswordHash.GetMd5Hash(password) == item.Password && item.IsActive == true
                        select 0;
            return query.Any();
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// check if a username already exists in database
    /// </summary>
    /// <param name="username">username to check</param>
    /// <returns>true if login info is correct, otherwise false</returns>
    public static bool UserExists(string username)
    {
        try
        {
            var query = from item in ApplicationGlobalVariables.KillDaBugsDatabase.Users
                        where item.Username == username
                        select 0;
            return query.Any();
        }
        catch (Exception ex)
        {
            return false;
            throw ex;
        }
    }

    /// <summary>
    /// Verifies if the text is is in a valid user name format like:
    /// is alphanumeric, starts with an alphabet and contains no
    /// special characters other than underscore or dash.
    /// Matches username | user123 |user_123 | user-123 | username123_type
    /// </summary>
    /// <param name="username">username to check</param>
    /// <returns>returns true if username is strong, otherwise false</returns>
    public static bool UsernameIsStrong(string username)
    {
        return System.Text.RegularExpressions.Regex.IsMatch(username, @"^([a-zA-Z])[a-zA-Z_-]*[\w_-]*[\S]$|^([a-zA-Z])[0-9_-]*[\S]$|^[a-zA-Z]*[\S]$");
    }

    /// <summary>
    /// Can be used for validating strong password.
    /// It expects atleast 1 small-case letter, 1 capital letter,
    /// 1 digit, 1 special character
    /// and the length should be between 6-20 characters.
    /// The sequence of the characters is not important.
    /// </summary>
    /// <param name="password">password to check</param>
    /// <returns>returns true if password is strong enough, otherwise false</returns>
    public static bool PasswordIsStrong(string password)
    {
        return System.Text.RegularExpressions.Regex.IsMatch(password, @"(?=^.{6,20}$)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&amp;*()_+}{&quot;:;'?/&gt;.&lt;,])(?!.*\s).*$");
    }

    /// <summary>
    /// Get the link to user profile page
    /// </summary>
    /// <param name="user">User to get the link</param>
    /// <returns>HTML link to the user profile page</returns>
    public static string GetTheUserLink(User user)
    {
        return "<a href='/User.aspx?username=" + user.Username + "'>" + user.Fullname + "</a>";
    }
}